/*
 *  Filename: duvoice.c
 *  Function: duvoice library
 *  Created: 2017-05-06
 *  Version: 1.0
 *  Authror: Bin
 */

/*-------- Includes --------*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

#include "duvoice.h"
#include "../cjson/cJSON.h"
#include "../base64/base64.h"

/*-------- Defines --------*/
#define MAX_HOSTURL_SIZE 512
#define MAX_HEADER_SIZE 256
#define MAX_POSTBODY_SIZE 512
#define MAX_FILENAME_SIZE 24

/*-------- Private variables --------*/
// user data
static char *cuid = "9590330";
static char *apiKey = "ZT7y5yGCywkbrq7XZgxdKCkP";
static char *secretKey = "b46b61e97725f34664f53e59dfcb1d22";
// audio info
static char *format = "wav";
static int rate = 16000;
static int channel = 1;
// text info
static char *lan = "zh";
static int ctp = 1;

/*-------- Private function prototypes --------*/
size_t WriteToBuf(void *ptr, size_t size, size_t nmemb, char **result);
size_t WriteToFile(void *ptr, size_t size, size_t nmemb, char *filename);
static void myfree(void *ptr);
static char *GetCurrentTime();
static void GetToken(const char *apiKey, const char *secretKey, char **token);
static char *STT_Post(const char *body);
static char *TTS_Post(const char *body);

/*-------- Private functions --------*/
size_t WriteToBuf(void *ptr, size_t size, size_t nmemb, char **result)
{
    size_t result_len = size * nmemb;
    *result = realloc(*result, result_len + 1);
    if (*result == NULL) {
        printf("realloc failure!\n");
        return 1;
    }
    memcpy(*result, ptr, result_len);
    (*result)[result_len] = '\0';
    return result_len;
}

size_t WriteToFile(void *ptr, size_t size, size_t nmemb, char *filename)
{
    FILE *fp;

    if ((fp = fopen(filename, "w")) == NULL) {
        perror("create mp3 file");
        return 0;
    }
    fwrite(ptr, size, nmemb, fp);
    fclose(fp);

    return size * nmemb;
}

void myfree(void *ptr)
{
    if (ptr != NULL)
        free(ptr);
}

char* GetCurrentTime()
{
    char *time_str = malloc(MAX_FILENAME_SIZE);
    if (time_str) {
        time_t now;
        struct tm *pnow;

        time(&now);
        pnow = localtime(&now);

        sprintf(time_str, "%.4d%.2d%.2d%.2d%.2d%.2d.mp3",
            pnow->tm_year+1900, pnow->tm_mon, pnow->tm_mday, pnow->tm_hour, pnow->tm_min, pnow->tm_sec);
    }

    return time_str;
}

void GetToken(const char *apiKey, const char *secretKey, char **token)
{
    char hosturl[MAX_HOSTURL_SIZE];
    char *resultBuf = NULL;
    // CURL variables
    CURL *curl;
    CURLcode res;

    // curl set options and perform
    int n = snprintf(hosturl, sizeof(hosturl),
        "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s",
        apiKey, secretKey);
    if (n > strlen(hosturl)) {
        fputs("GetToken: hosturl\n", stderr);
        return;
    }
    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, hosturl);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToBuf);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resultBuf);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK)
            fprintf(stderr, "curl: %s\n", curl_easy_strerror(res));
        curl_easy_cleanup(curl);
    }

    // parse cJSON result and release buffer
    cJSON *root = cJSON_Parse(resultBuf);
    myfree(resultBuf);
    if (root != NULL) {
        cJSON *tmp;
        tmp = cJSON_GetObjectItem(root, "access_token");
        *token = malloc(strlen(tmp->valuestring) + 1);
        if (*token != NULL)
            strcpy(*token, tmp->valuestring);
        cJSON_Delete(root);
    }
}

char *STT_Post(const char *body)
{
    char tmp[MAX_HEADER_SIZE];
    char hosturl[MAX_HOSTURL_SIZE];
    int content_len = strlen(body);
    char *resultBuf = NULL;
    // CURL variables
    CURL *curl;
    CURLcode res;
    struct curl_slist *headerlist = NULL;

    // set hosturl
    strncpy(hosturl, "http://vop.baidu.com/server_api", sizeof(hosturl));

    // set headerlist
    headerlist = curl_slist_append(headerlist,
        "Content-Type: application/json; charset=utf-8");
    snprintf(tmp, sizeof(tmp), "Content-Length: %d", content_len);
    headerlist = curl_slist_append(headerlist, tmp);

    // curl set options and perform
    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, hosturl);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, content_len);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToBuf);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resultBuf);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK)
            fprintf(stderr, "curl: %s\n", curl_easy_strerror(res));
        // release headerlist and curl
        curl_slist_free_all(headerlist);
        curl_easy_cleanup(curl);
    }

    // parse cJSON result and release buffer
    cJSON *root = cJSON_Parse(resultBuf);
    myfree(resultBuf);
    resultBuf = NULL;
    if (root != NULL) {
        cJSON *tmp = cJSON_GetObjectItem(root, "err_no");
        if (tmp->valueint == 0) {
            tmp = cJSON_GetObjectItem(root, "result")->child;
            if ((resultBuf = malloc(strlen(tmp->valuestring) + 1)) == NULL) {
                fputs("malloc: STT_Post\n", stderr);
                return NULL;
            }
            strcpy(resultBuf, tmp->valuestring);
        } else {
            return NULL;
        }
        cJSON_Delete(root);
    }

    return resultBuf;
}

char *TTS_Post(const char *body)
{
    char hosturl[MAX_HOSTURL_SIZE];
    int content_len = strlen(body);
    char *filename = NULL;
    // CURL variables
    CURL *curl;
    CURLcode res;
    char *contentType = NULL;

    // set filename
    filename = GetCurrentTime();
    // set hosturl
    strncpy(hosturl, "http://tsn.baidu.com/text2audio", sizeof(hosturl));

    // curl set options and perform
    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, hosturl);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, content_len);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteToFile);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, filename);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK)
            fprintf(stderr, "curl: %s\n", curl_easy_strerror(res));
        curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType);
        if (strcmp(contentType, "audio/mp3") == 0)
            return filename;
        printf("%s\n", contentType);
        curl_easy_cleanup(curl);
    }

    return NULL;
}

/*-------- Exported functions --------*/
duVoice *duVoice_Init()
{
    duVoice *handle = malloc(sizeof(duVoice));

    if (handle) {
        GetToken(apiKey, secretKey, &handle->token);
        handle->speech = NULL;
        handle->text = NULL;
    }

    return handle;
}
void duVoice_Delete(duVoice *handle)
{
    if (handle) {
        myfree(handle->token);
        myfree(handle->speech);
        myfree(handle->text);

        free(handle);
    }
}

void duVoice_SetSpeech(duVoice *handle, const char *speech)
{
    if (handle) {
        handle->speech = malloc(strlen(speech) + 1);
        if (handle->speech)
            strcpy(handle->speech, speech);
    }
}
void duVoice_SetText(duVoice *handle, const char *text)
{
    if (handle) {
        handle->text = malloc(strlen(text) + 1);
        if (handle->text)
            strcpy(handle->text, text);
    }
}

void duVoice_Cleanup(duVoice *handle)
{
    if (handle) {
        myfree(handle->speech);
        handle->speech = NULL;
        myfree(handle->text);
        handle->text = NULL;
    }
}


char *duVoice_Recognize(duVoice *handle)
{
    FILE *fp = NULL;
    unsigned char *audio_data = NULL;
    int audio_len;
    char *base64_data = NULL;
    char *JSON_body = NULL;

    // check and reset handle
    if (handle->token == NULL || handle->speech == NULL || handle->text != NULL) {
        fputs("invalid handle\n", stderr);
        return NULL;
    }

    // open audio file
    if ((fp = fopen(handle->speech, "r")) == NULL) {
        perror("fopen");
        return NULL;
    }

    // get audio file length
    fseek(fp, 0, SEEK_END);
    audio_len = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    // read audio file to ram
    if ((audio_data = malloc(audio_len)) == NULL) {
        fputs("malloc: audio_data\n", stderr);
        goto recognize_exit;
    }
    fread(audio_data, sizeof(unsigned char), audio_len, fp);

    // encode
    if ((base64_data = base64_encode(audio_data, audio_len)) == NULL) {
        fputs("base64_encode: failure\n", stderr);
        goto recognize_exit;
    }

    // create POST JSON body
    cJSON *root = cJSON_CreateObject();
    cJSON_AddItemToObject(root, "format", cJSON_CreateString(format));
    cJSON_AddItemToObject(root, "rate", cJSON_CreateNumber(rate));
    cJSON_AddItemToObject(root, "channel", cJSON_CreateNumber(channel));
    cJSON_AddItemToObject(root, "token", cJSON_CreateString(handle->token));
    cJSON_AddItemToObject(root, "cuid", cJSON_CreateString(cuid));
    cJSON_AddItemToObject(root, "speech", cJSON_CreateString(base64_data));
    cJSON_AddItemToObject(root, "len", cJSON_CreateNumber(audio_len));
    JSON_body = cJSON_PrintUnformatted(root);
    cJSON_Delete(root);
    if (JSON_body == NULL) {
        fputs("create JSON body: failure\n", stderr);
        goto recognize_exit;
    }

    // speech to text POST
    if ((handle->text = STT_Post(JSON_body)) == NULL)
        fputs("STT_Post: failure\n", stderr);

recognize_exit:
    myfree(audio_data);
    myfree(base64_data);
    myfree(JSON_body);
    fclose(fp);

    return handle->text;
}

char *duVoice_Synthesize(duVoice *handle)
{
    char body[MAX_POSTBODY_SIZE];
    int n = 0;  // snprintf return value

    // check and reset handle
    if (NULL == handle->token || NULL == handle->text || NULL != handle->speech) {
        fputs("invalid handle\n", stderr);
        return NULL;
    }

    // set postbody
    n = snprintf(body, sizeof(body), "tex=%s&lan=%s&tok=%s&ctp=%d&cuid=%s",
        handle->text, lan, handle->token, ctp, cuid);
    if (n > strlen(body)) {
        fputs("Synthesize: set postbody failure\n", stderr);
        return NULL;
    }

    // text to speech POST
    if ((handle->speech = TTS_Post(body)) == NULL)
        fputs("TTS_Post: failure\n", stderr);

    return handle->speech;
}
